<?php
/**
 * Основной класс системы. Позволяет загружать модули и представления,
 * а также содержит все глобальные объекты.
 * Создан: 2011-02-01
 * 
 * @author djerom
 * @version 0.0.0.2
 *  
 */

class c_mvc_util
{
    var $config = array();
    var $messages = array();
	var $hooks = array(); // Массив хуков. Загружается из файла.
	var $not_found_hooks = array(); // Вызываемые, но не существующие хуки. Автоматом создаются при следующем запуске.
	var $data = array(); // Различные пользовательские данные.

	// Деструктор класса. Здесь сохраняем разные нужные нам данные.
	function __destruct()
	{	
		// Если есть несуществующие хуки, то добавим их к существующим.
		if (sizeof($this->not_found_hooks))
		{
			// В процессе работы могли понаписать сюда всякого, поэтому загрузим хуки еще раз.
			$this->load_hooks();
			// Добавим недостающие.
			$this->hooks = array_merge($this->hooks, $this->not_found_hooks);
			// Сохраним полный список.
			$this->save_hooks();
		}
	}
	
    // Заменить в строке вставки вида {name}, на парметры конфига.
    function parse($text, $data = 0, $use_config = true)
	{
        if ($data == 0){
            $data = $this->config;
        }elseif ($use_config){
            $data = array_merge($this->config, $data);
        }

        $steps = 100;
        $p1 = strpos($text, '{');
        while ($p1++ !== false) {
            $p2 = strpos($text, '}', $p1);
            $sub = substr($text, $p1, $p2 - $p1);
            $text = str_replace('{' . $sub . '}', $data[$sub], $text);
            $p1 = strpos($text, '{');
            if ($steps-- == 0){
                die('Too many steps.');
            }
        }

        return $text;
    }

    // Получить из массива $args значения параметра с именем $name или индексом $ind.
    // Если значение не найдено, то вернется $def.
    function getVal($args, $name, $ind, $def = NULL) {
        if (isset($args[$name])) return $args[$name];
        if (isset($args[$ind]))  return $args[$ind];
        return $def;
    }

    // Добавляет строчку в лог.
    // type - тип сообщения ([0] | 1 | 2) -> (message | warning | error)
    function message($text, $level = 'message', $class_instance = 0) {
		if ($class_instance) $class_instance = get_class($class_instance);
        $this->messages[] = array('text' => $text, 'level' => $level, 'class' => $class_instance);
    }

    // Загрузить view. Создается экземпляр view с параметром путь к файлу и возвращается.
    function load_view($name) { //+++deprecated, use $mvc->view->render($name) instead
        $path = $this->parse('{base_path}views/' . $name . '.php');
        $view = $this->load_module('view');
        $view->load($path);
        return $view;
    }

    // Выдать страницу 404 (или другую страницу обработки ошибки) и завершить работу.
    function send_error_page() {
        global $modx;
        if ($modx) {
            $modx->sendErrorPage();
			die();
        }
		else
	        die('Error 404. Page not found.');
    }

	// Перегрузка $mvc->lang->tr
	function tr($text)
	{
		return $this->lang->tr($text);
	}

    /**
     * Транслитерация (для очистки имен загружаемых файлов и т.д.)
     * @global DocumentParser $modx
     * @param string $text
     * @return string
     */
    function translit($text) {
        global $modx;

        require_once(MODX_BASE_PATH . 'assets/plugins/transalias/transalias.class.php');
        $trans = new TransAlias($modx);
        $trans->loadTable("russian", "No");
        return $trans->stripAlias($text, "lowercase alphanumeric", "dash");
    }

    /**
     * Преобразует байты в размер
     * @param int $size
     * @return string
     */
    function format_bytes($size) {
        $units = array(' Б', ' кБ', ' МБ', ' ГБ', ' ТБ');
        for ($i = 0; $size >= 1024 && $i < 4; $i++) $size /= 1024;
        return round($size, 2).$units[$i];
    }

    function full_clear_cache(){
        global $modx;
        include_once MODX_BASE_PATH."manager/processors/cache_sync.class.processor.php";
        $sync = new synccache();
        $sync->setCachepath("assets/cache/");
        $sync->setReport(false);
        $sync->emptyCache();
        $modx->invokeEvent("OnSiteRefresh");
    }
	
	// Проверяет каждый ключ таблицы по маске $mask. Если ключ подходит, то в результирующий массив попадет
	// пара (ключ) => значение, где (ключ) выделяемая часть ключа в маске.
	// Например при вызове extract_data(('f_id'=> 1, 'f_number' => 2, 'fff' => 88), '/f_(\\w+)/') вернется массив
	// ('id' => 1, 'number' => 2).
	function extract_data($array, $mask)
	{
		$result = array();
		foreach($array as $k => $v)
		{
			if (preg_match($mask, $k, $subs))
			{
				$result[$subs[1]] = $v;
			}
		}
		return $result;
	}
	
	// Сохранить массив в файл.
	function save_array($array, $filename)
	{
		// Создать папку если ее не было.
		if (!file_exists( dirname($filename))) mkdir(dirname($filename), 0777, true);
			
		$f = fopen($filename, 'w');
		fwrite($f, '<?php /*'.serialize($array).'*/?>');
		fclose($f);
	}
	
	// Загрузить массив из файла.
	function load_array($filename)
	{
		if (file_exists($filename))
		{
			$f = fopen($filename, 'r');
			fread($f, 8);
			$data = fread($f, filesize($filename) - 12);
			$data = unserialize($data);
			fclose($f);
			return $data;
		}
		else
			return array();
	}
	
	// Заполняет пути $class_instance в его системные переменные.
	function fill_class_vars($class_instance)
	{
		$name = substr(get_class($class_instance), 2);
		// Имя модуля. Часть имени класса c_(имя_класса)_[util | model | controller | ...]
		$class_instance->module = substr($name, 0, strrpos($name, '_'));
		
		// Ссылка на модуль(для контроллеров).
		$class_instance->base_url = $this->config['base_url'].$class_instance->module.'/';
		
		// Путь к папке с данными.
		$class_instance->data_path = $this->config['base_path'].'data/'.$class_instance->module.'/'; 
		// Ссылка на папку с данными.
		$class_instance->data_url = $this->config['base_url'].'data/'.$class_instance->module.'/'; 
		
		// Путь к папке с данными.
		$class_instance->view_path = $this->config['base_path'].'views/'.$class_instance->module.'/'; 
		// Ссылка на папку с данными.
		$class_instance->view_url = $this->config['base_url'].'views/'.$class_instance->module.'/'; 
	}
	
	// Перенаправялет пользователя на адрес url.
	function redirect($url)
	{	
		global $mvc;
		
		if (substr($url, 0, 5) == 'http:')
		{
			header('Location: '.$url);
		}
		else
		{
			header('Location: '.$mvc->config['base_url'].$url);
		}
		die();
	}
	
	// Загружаем хуки.
	function load_hooks()
	{
		$this->hooks = $this->load_array($this->data_path.'hooks.php');
	}
	
	// Сохранить хуки.
	function save_hooks()
	{
		$this->save_array($this->hooks, $this->config['base_path'].'data/mvc/hooks.php');
	}
	
	// Добавить хук. $event - имя события, $receiver(строка) - описание получателя 'function' или 'class.method'.
	function add_hook($event, $receiver)
	{
		if (!isset($this->hooks[$event])) $this->hooks[$event] = array();
		if (array_search($receiver, $this->hooks[$event]) === false)
			$this->hooks[$event][] = $receiver;
	}
	
	// Удалить хук. $event - имя события, $receiver(строка) - описание получателя 'function' или 'class.method'.
	function del_hook($event, $receiver)
	{
		if (!isset($this->hooks[$event])) return;
		$this->hooks[$event] = array_diff($this->hooks[$event], array($receiver));
	}
	
	// Генерировать событие(хук). Все подписчики получат сигнал о том, что событие произошло.
	// $sender - отправитель сообщения обычно $this.
	// $event - имя события.
	// $args - параметры, которые будут переданы дальше.
	function emit($event, $args = array(), $sender = 0)
	{
		global $mvc;
		
		if (!is_array($args)) $args = array('arg' => $args);
		$args['_sender'] = $sender ? $sender : 0;
		$args['_event'] = $event;
		
		if (isset($this->hooks[$event]))
		{
			ob_start(); // Всеь вывод соберем в кучу и вернем как результат, а не просто выведем.
			$ret = '';
			foreach($this->hooks[$event] as $hook)
			{
				//echo $hook;
				//die();
				if ($hook[0] == '$')
				{
					echo 'Will eval';
					$ret .= eval($hook.';');
				}
				else
				{			
					// Режем по .
					$phook = explode('.', $hook);
					// 1 параметр - это функция.
					if (sizeof($phook) < 2)
					{
						if (function_exists($phook[0]))
							$ret .= call_user_func($phook[0], $args);
						else
							Throw new Exception('Function '.$phook[0].' not found while hook calling.'); // TODO: Скрыть для неавторизованных
					}
					// Это метод класса. Пробуем создать и вызвать.
					else
					{
						$instance = new $phook[0]();
						if (method_exists($instance, $phook[1]))
							$ret .= call_user_method($phook[1], $instance, $args);
						else
							Throw new Exception('Method '.$phook[0].'.'.$phook[1].' not found while hook calling.'); // TODO: Скрыть для неавторизованных
					}
				}
			}
			return ob_get_clean().$ret;
		}
		else
		{
			// Добавим ненайденный хук в список, чтобы можно было его настраивать.
			$this->not_found_hooks[$event] = array();
		}
	}
}
?>